#include "carADT.h"
#include "cityADT.h"
#include "semaphoreADT.h"
#include <stdlib.h>
#include <pthread.h>

#define STREET_W 2

#define SEMAPHORE_STATES()

#define INIT_CARS 5
#define CARS_EXPAND_SIZE 5


struct cityCDT{
	carADT * cars;
	int currentNumCars;
	int currentMaxNumCars;
	semaphoreADT** semaphores;
	int semaphoreMatrixWidth;
	int semaphoreMatrixHeight;
	int numBlocksX;
	int numBlocksY;
	int blockSizeX;
	int blockSizeY;
	pthread_mutex_t mutex;
} cityCDT;

void transformCoordenates(cityADT city,int x,int y,int* semCoorX, int* semCoorY);

static int expandMaxNumCars(cityADT city);


cityADT
newCity(int numBlocksX, int numBlocksY, int blockSizeX, int blockSizeY)
{
	struct cityCDT * city;
	int i;
	

	if(numBlocksX<=0 || numBlocksY <=0 || blockSizeX<=0 || blockSizeY<=0 )
		return NULL;
		
	if((city=malloc(sizeof(struct cityCDT)))==NULL)
		return NULL;

	
	city->numBlocksX=numBlocksX;
	city->numBlocksY=numBlocksY;
	city->blockSizeX=blockSizeX;
	city->blockSizeY=blockSizeY;
	
	pthread_mutex_init(&city->mutex, NULL); 

	city->cars = malloc(sizeof(carADT) * INIT_CARS);
	if(city->cars == NULL)
	{
		free(city);
		return NULL;
	}

	city->currentNumCars = 0;
	city->currentMaxNumCars = INIT_CARS;

	city->semaphoreMatrixWidth = numBlocksX * STREET_W;
	city->semaphoreMatrixHeight = numBlocksY * STREET_W;
	
	/*Alocate space to the corners */
	if((city->semaphores = malloc(sizeof(semaphoreADT*) * city->semaphoreMatrixWidth)) == NULL)
	{
		free(city);
		return NULL;
	}
	
	for(i=0;i<numBlocksX * STREET_W; i++)
	{
		city->semaphores[i] = calloc(city->semaphoreMatrixHeight, sizeof(semaphoreADT));
		if(city->semaphores[i] == NULL)
		{
			if(i==0)
				return NULL;
			
			while(i--)
				free(city->semaphores[i]);
			free(city);
			return NULL;
		}
	}

	return city;
}


int
registerCar(cityADT city, void* car)
{
	pthread_mutex_lock(&city->mutex);
	city->cars[city->currentNumCars] = (carADT)car;

	city->currentNumCars++;
	if(city->currentNumCars == city->currentMaxNumCars)
		if(!expandMaxNumCars(city))
		{
			pthread_mutex_unlock(&city->mutex);
			return 0;
		}

	pthread_mutex_unlock(&city->mutex);
	return 1;
}

int
unRegisterCar(cityADT city, void * car)
{
	pthread_mutex_lock(&city->mutex);
	city->cars[city->currentNumCars - 1] = NULL;
	city->currentNumCars--;
	pthread_mutex_unlock(&city->mutex);
	return 1;
}

int
unRegisterSemaphore(cityADT city, int x, int y)
{
	pthread_mutex_lock(&city->mutex);
	int semX, semY;

	transformCoordenates(city,x,y, &semX, &semY);

	if(semX >= city->semaphoreMatrixWidth || semY >= city->semaphoreMatrixHeight)
	{	
		pthread_mutex_unlock(&city->mutex);
		return 0;
	}

	city->semaphores[semX][semY] = NULL;
	pthread_mutex_unlock(&city->mutex);
	return 1;
}

int
registerSemaphore(cityADT city, void* semaphore, int x, int y)
{
	pthread_mutex_lock(&city->mutex);
	int semX, semY;

	transformCoordenates(city,x,y, &semX, &semY);
	if(semX >= city->semaphoreMatrixWidth || semY >= city->semaphoreMatrixHeight)
	{
		pthread_mutex_unlock(&city->mutex);
		return 0;
	}

	city->semaphores[semX][semY] = semaphore;
	
	pthread_mutex_unlock(&city->mutex);
	return 1;
}


static int
expandMaxNumCars(cityADT city)
{
	carADT* auxCars;

	auxCars = realloc(city->cars, (city->currentMaxNumCars + CARS_EXPAND_SIZE) * sizeof(carADT));

	if(auxCars == NULL)
		return 0;

	city->currentMaxNumCars += CARS_EXPAND_SIZE;
	city->cars = auxCars;
	return 1;
}


void
freeCity(cityADT city)
{
	int i;
	
	for(i=0;i<city->semaphoreMatrixWidth; i++)
	{
		free(city->semaphores[i]);
	}
	free(city->semaphores);
	free(city->cars);
	free(city);
}


//callerId is the id of the car who checks this position
void*
hasThisPositionACar(cityADT city, int x, int y, int callerId)
{
	pthread_mutex_lock(&city->mutex);
	carADT nextCar;
	int i;

	for(i=0; i < city->currentNumCars; i++)
	{
		if ((nextCar = city->cars[i]) == NULL)
			continue;
		if(getCarX(nextCar)==x && getCarY(nextCar)==y && getCarId(nextCar) != callerId)
		{
			pthread_mutex_unlock(&city->mutex);
			return nextCar;
		}
	}
	pthread_mutex_unlock(&city->mutex);
	return NULL;
}



int
isCorner(cityADT city, int x, int y)
{
	int xBlockSize = 2+city->blockSizeX;
	int yBlockSize = 2+city->blockSizeY;

	
	if(((x % xBlockSize) == 0 ||	(x%xBlockSize) == 1) &&
		((y % yBlockSize) == 0 ||	(y%yBlockSize) == 1))
		return TRUE;
	else
		return FALSE;
}


void*
getSemaphore(cityADT city, int x, int y)
{
	pthread_mutex_lock(&city->mutex);
	int semCoorX, semCoorY;

	
	if(!isCorner(city, x, y))
	{
		pthread_mutex_unlock(&city->mutex);
		return NULL;
	}

	if(x<(city->numBlocksX*(city->blockSizeX +2)) &&
	y<(city->numBlocksY*(city->blockSizeY +2)))
	{
		transformCoordenates(city, x, y, &semCoorX, &semCoorY);
		pthread_mutex_unlock(&city->mutex);
		return city->semaphores[semCoorX][semCoorY];
	}
	else
	{
		pthread_mutex_unlock(&city->mutex);
		return NULL;
	}
}



int
hasSemaphore(cityADT city, int x, int y)
{		
	return getSemaphore(city, x, y) != NULL;
}


void*
hasSemaphoreInRed(cityADT city, int x, int y,int orientation)
{
	semaphoreADT sem;
	sem = getSemaphore(city, x, y);
	
	return sem;	

}


void
transformCoordenates(cityADT city,int x,int y,int* semCoorX, int* semCoorY)
{
	*semCoorX=0;
	*semCoorY=0;
	while(x > city->blockSizeX)
	{
		x -= (city->blockSizeX + 2);
		(*semCoorX)++;
	}
	while(y > city->blockSizeY)
	{
		y -= (city->blockSizeY + 2);
		(*semCoorY)++;
	}
	
	return;
	
}

int
getCityWidth(cityADT city)
{
	return (city->blockSizeX + 2) * city->numBlocksX;
}

int
getCityHeight(cityADT city)
{
	return (city->blockSizeY + 2) * city->numBlocksY;
}
